package lk.mrt.cse.wihidum.communicator;

import lk.mrt.cse.wihidum.storage.*;
import lk.mrt.cse.wihidum.util.WihidumConstants;
import lk.mrt.cse.wihidum.util.WSEventingConstants;
import lk.mrt.cse.wihidum.processor.PublicationMessage;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.soap.*;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.OperationClient;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.MessageContext;
import org.apache.log4j.Logger;

import javax.xml.namespace.QName;
import java.util.ArrayList;

public class TreeRouter
        implements Router {

    Topology topology;
    private static final Logger logger = Logger.getLogger(TreeRouter.class);

    public TreeRouter() {
        XMLReader xmlReader = new XMLReader("TreeConfig.xml");
        topology = xmlReader.readXmlDocument();
    }

    public void propagateSubscription(SOAPEnvelope message, String filter, String action) {
        String incommingAddress = null;
        String incommingUnit = null;

        String myAddress = topology.getMyAddress();

        if (action.equals(WihidumConstants.Actions.Subscribe)) {
            incommingUnit = "LocalUnit";
            logger.info("The subscription was received directly without propagation");
        } else if (action.equals(WihidumConstants.Operations.BROKER_SUBSCRIBE)) {
            try {
                SOAPBody body = message.getBody();
                OMElement addressElement = body.getFirstChildWithName(new QName("BrokerSubscription"))
                        .getFirstChildWithName(new QName("From"));
                incommingAddress = addressElement.getText();
            } catch (Exception e) {
                logger.error(e);
            }
        }

        for (NeighbourBroker broker : topology.getSuperUnits()) {
            if (broker.getAddress().equals(incommingAddress)) {
                incommingUnit = "SuperUnit";
                break;
            }
        }
        for (NeighbourBroker broker : topology.getSubUnits()) {
            if (broker.getAddress().equals(incommingAddress)) {
                incommingUnit = "SubUnit";
                break;
            }
        }
        if (incommingUnit == null) {
            logger.info("Cannot propagate the message");
        } else if (incommingUnit.equals("LocalUnit")) {
            for (NeighbourBroker broker : getSuperUnits()) {
                SOAPEnvelope envelope = updateBrokerSubscription(broker.getAddress(), myAddress, filter);
                forwardSubscription(envelope, broker.getAddress());
            }
            for (NeighbourBroker broker : getSubUnits()) {
                SOAPEnvelope envelope = updateBrokerSubscription(broker.getAddress(), myAddress, filter);
                forwardSubscription(envelope, broker.getAddress());
            }
        } else if (incommingUnit.equals("SuperUnit")) {
            for (NeighbourBroker broker : getSubUnits()) {
                SOAPEnvelope envelope = updateBrokerSubscription(broker.getAddress(), myAddress, filter);
                forwardSubscription(envelope, broker.getAddress());
            }
        } else if (incommingUnit.equals("SubUnit")) {
            for (NeighbourBroker broker : getSuperUnits()) {
                SOAPEnvelope envelope = updateBrokerSubscription(broker.getAddress(), myAddress, filter);
                forwardSubscription(envelope, broker.getAddress());
            }
            for (NeighbourBroker broker : getSubUnits()) {
                if (!broker.getAddress().equals(incommingAddress)) {
                    SOAPEnvelope envelope = updateBrokerSubscription(broker.getAddress(), myAddress, filter);
                    forwardSubscription(envelope, broker.getAddress());
                }
            }
        }
    }

    private SOAPEnvelope updateBrokerSubscription(String toAddress, String fromAddress, String filter) {
        logger.info("Updating the broker subscription");

        SOAPFactory factory = OMAbstractFactory.getSOAP11Factory();

        SOAPEnvelope envelop = factory.getDefaultEnvelope();

        OMNamespace eventingNS = factory.createOMNamespace(WSEventingConstants.EVENTING_NAMESPACE, WSEventingConstants.EVENTING_PREFIX);
        envelop.declareNamespace(eventingNS);
        OMNamespace addressingNS = factory.createOMNamespace(WSEventingConstants.ADDRESSING_NAMESPACE, WSEventingConstants.ADDRESSING_PREFIX);
        envelop.declareNamespace(addressingNS);
        OMNamespace wihidumNS = factory.createOMNamespace(WSEventingConstants.WIHIDUM_NAMESPACE, WSEventingConstants.WHIDUM_NAMESPACE_PREFIX);
        envelop.declareNamespace(wihidumNS);

        OMElement brokerSubscriptionElement = factory.createOMElement("BrokerSubscription", wihidumNS);
        OMElement actionElement = factory.createOMElement("Action", wihidumNS);
        OMElement toElement = factory.createOMElement("To", wihidumNS);
        OMElement toAddressElement = factory.createOMElement("Address", wihidumNS);
        OMElement fromElement = factory.createOMElement("From", wihidumNS);
        OMElement fromAddressElement = factory.createOMElement("Address", wihidumNS);
        OMElement filterElement = factory.createOMElement("Filter", wihidumNS);
        OMElement distributionTraceElement = factory.createOMElement("DistributionState", wihidumNS);
        OMElement startStateElement = factory.createOMElement("StartState", wihidumNS);

        actionElement.setText(WihidumConstants.Operations.BROKER_SUBSCRIBE);
        toAddressElement.setText(toAddress);
        fromAddressElement.setText(fromAddress);
        filterElement.setText(filter);
        startStateElement.setText("Start");

        toElement.addChild(toAddressElement);
        fromElement.addChild(fromAddressElement);
        distributionTraceElement.addChild(startStateElement);
        brokerSubscriptionElement.addChild(actionElement);
        brokerSubscriptionElement.addChild(toElement);
        brokerSubscriptionElement.addChild(fromElement);
        brokerSubscriptionElement.addChild(filterElement);

        envelop.getBody().addChild(brokerSubscriptionElement);
        envelop.getBody().addChild(distributionTraceElement);

        System.out.println(envelop);

        return envelop;
    }

    private void forwardSubscription(SOAPEnvelope message, String address) {

        logger.info("Propogating subscription message");

        try {
            ServiceClient serviceClient = new ServiceClient();
            OperationClient opClient = serviceClient.createClient(ServiceClient.ANON_OUT_IN_OP);
            MessageContext outMsgCtx = new MessageContext();
            Options opts = outMsgCtx.getOptions();
            opts.setTo(new EndpointReference(address));
            opts.setAction(WihidumConstants.Operations.BROKER_SUBSCRIBE);
            outMsgCtx.setEnvelope(message);
            opClient.addMessageContext(outMsgCtx);
            opClient.execute(true);
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    //handling the propagation of broker publication messages
    public void propagatePublication(PublicationMessage message) {

        String incomingAddress;
        String incomingUnit = null;

        try {
            incomingAddress = message.getBrokerPublicationElementFrom();

            for (NeighbourBroker neighbourBroker : getSubUnits()) {
                if (neighbourBroker.getAddress().equals(incomingAddress)) {
                    incomingUnit = "SubUnit";
                    break;
                }
            }

            for (NeighbourBroker neighbourBroker : getSuperUnits()) {
                if (neighbourBroker.getAddress().equals(incomingAddress)) {
                    incomingUnit = "SuperUnit";
                    break;
                }
            }

            if (incomingUnit == null) {
                logger.error("Cannot propagate the message!");
            } else if (incomingUnit.equals("SuperUnit")) {
                for (NeighbourBroker neighbourBroker : getSuperUnits()) {
                    if (!neighbourBroker.getAddress().equals(incomingAddress)) {
                        SOAPEnvelope soapEnvelope = createBrokerPublicationMessage(message, neighbourBroker.getAddress());
                        forwardPublication(soapEnvelope, neighbourBroker.getAddress());
                    }
                }

                for (NeighbourBroker neighbourBroker : getSubUnits()) {
                    SOAPEnvelope soapEnvelope = createBrokerPublicationMessage(message, neighbourBroker.getAddress());
                    forwardPublication(soapEnvelope, neighbourBroker.getAddress());
                }

            } else if (incomingUnit.equals("SubUnit")) {
                for (NeighbourBroker neighbourBroker : getSuperUnits()) {
                    SOAPEnvelope soapEnvelope = createBrokerPublicationMessage(message, neighbourBroker.getAddress());
                    forwardPublication(soapEnvelope, neighbourBroker.getAddress());
                }

                for (NeighbourBroker neighbourBroker : getSubUnits()) {
                    if (!neighbourBroker.getAddress().equals(incomingAddress)) {
                        SOAPEnvelope soapEnvelope = createBrokerPublicationMessage(message, neighbourBroker.getAddress());
                        forwardPublication(soapEnvelope, neighbourBroker.getAddress());
                    }
                }
            }
        } catch (Exception e) {
            logger.error("Error occured while propagating the publication message!");
        }
    }

    //Creating the publication message that will be forwarded through the tree later on.
    private SOAPEnvelope createBrokerPublicationMessage(PublicationMessage msg, String address) {

        logger.info("Updating broker publication header!");

        SOAPFactory factory = OMAbstractFactory.getSOAP11Factory();
        SOAPEnvelope brokerPubEnvelope = factory.getDefaultEnvelope();
        SOAPBody soapEnvelopeBody = brokerPubEnvelope.getBody();
        SOAPHeader soapHeader = brokerPubEnvelope.getHeader();

        OMElement brokerPubHeaderElement = factory.createOMElement("BrokerPublicationHeader", null);
        OMElement brokerPubActionElement = factory.createOMElement("Action", null);
        brokerPubActionElement.setText(WihidumConstants.Operations.BROKER_PUBLISH);
        brokerPubHeaderElement.addChild(brokerPubActionElement);

        OMElement toElement = factory.createOMElement("To", null);
        OMElement toAddressElement = factory.createOMElement("Address", null);
        toAddressElement.setText(address);
        toElement.addChild(toAddressElement);
        brokerPubHeaderElement.addChild(toElement);

        OMElement fromElement = factory.createOMElement("From", null);
        OMElement fromAddressElement = factory.createOMElement("Address", null);
        fromAddressElement.setText(topology.getMyAddress());
        fromElement.addChild(fromAddressElement);
        brokerPubHeaderElement.addChild(fromElement);

        OMElement distributionTraceElement = factory.createOMElement("DistributionTrace", null);
        OMElement startStationElement = factory.createOMElement("StartStation", null);
        startStationElement.setText(msg.getBrokerPublicationElementStartStation());
        distributionTraceElement.addChild(startStationElement);
        OMElement intermediateStationElement = factory.createOMElement("IntermediateStation", null);
        intermediateStationElement.setText(topology.getMyId());
        distributionTraceElement.addChild(intermediateStationElement);
        brokerPubHeaderElement.addChild(distributionTraceElement);

        soapHeader.addChild(brokerPubHeaderElement);

        OMElement pubHeaderElement = factory.createOMElement("PublicationHeader", null);
        OMElement replyToElement = factory.createOMElement("ReplyTo", null);
        OMElement replyToAddressElement = factory.createOMElement("Address", null);
        replyToAddressElement.setText(topology.getMyAddress());
        replyToElement.addChild(replyToAddressElement);
        pubHeaderElement.addChild(replyToElement);

        OMElement topicElement = factory.createOMElement("Topic", null);
        topicElement.setText(msg.getTopic());
        pubHeaderElement.addChild(topicElement);

        soapHeader.addChild(pubHeaderElement);
        soapEnvelopeBody.setText(msg.getBody());

        return brokerPubEnvelope;
    }

    //Forwarding the broker publication message to the desired node.
    private void forwardPublication(SOAPEnvelope message, String address) {

        logger.info("Propagating Publication messages");

        try {
            ServiceClient serviceClient = new ServiceClient();
            OperationClient operationClient = serviceClient.createClient(ServiceClient.ANON_OUT_IN_OP);
            MessageContext outMessageContext = new MessageContext();
            Options options = outMessageContext.getOptions();
            options.setTo(new EndpointReference(address));
            options.setAction(WihidumConstants.Operations.BROKER_PUBLISH);

            outMessageContext.setEnvelope(message);
            operationClient.addMessageContext(outMessageContext);
            operationClient.execute(true);

        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    public SOAPEnvelope createPublicationRequest(PublicationMessage publicationMessage, NeighbourBroker superUnit, String SOAPURI) {
        try {
            SOAPFactory soapFactory = null;
            if (SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(SOAPURI))
                soapFactory = OMAbstractFactory.getSOAP11Factory();
            else if (SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(SOAPURI))
                soapFactory = OMAbstractFactory.getSOAP12Factory();
            else throw new Exception("Unknown SOAP version");

            SOAPEnvelope soapEnvelope = soapFactory.getDefaultEnvelope();
            return soapEnvelope;
        }
        catch (Exception ex) {
            logger.error(ex);
            return null;
        }
    }


    public ArrayList<NeighbourBroker> getSuperUnits() {
        return topology.getSuperUnits();
    }

    public ArrayList<NeighbourBroker> getPeerUnits() {
        return topology.getPeerUnits();
    }

    public ArrayList<NeighbourBroker> getSubUnits() {
        return topology.getSubUnits();
    }

    public SOAPEnvelope createResponse() {
        return null;
    }
}
